//cDrawBox.cpp
#include "cDrawBox.h"

//Used by signal handlers to identify a particular instance of the class
//given its DrawingArea, by iterating through this
std::list<DrawBox*> DrawBox::Instances;

/* Default constructor.
 */
DrawBox::DrawBox()
{
	this->_Create(200, 200);
}

/* Constructor with size.
 * Inputs:
 * -Width, Height: Dimensions of box.
 */
DrawBox::DrawBox(u32 Width, u32 Height)
{
	this->_Create(Width, Height);
}

/* PROTECTED: Internal constructor routine.
 * Inputs:
 * -Width, Height: Dimensions of box.
 */
void DrawBox::_Create(u32 Width, u32 Height)
{
	this->Width = Width;
	this->Height = Height;
	this->BackBuf = NULL;
	this->PLayout = NULL;
	
	//Create the drawing area and connect signals.
	this->DrawingArea = gtk_drawing_area_new();
	gtk_widget_set_size_request(this->DrawingArea, this->Width, this->Height);
    gtk_signal_connect(GTK_OBJECT(this->DrawingArea), "expose-event",  
		(GtkSignalFunc)DrawBoxExposeEvent, this);
	gtk_signal_connect(GTK_OBJECT(this->DrawingArea),"configure-event",
		(GtkSignalFunc)DrawBoxConfigureEvent, this);
	gtk_signal_connect(GTK_OBJECT(this->DrawingArea), "motion-notify-event",
		(GtkSignalFunc)DrawBoxMotionEvent, this);
	gtk_signal_connect(GTK_OBJECT(this->DrawingArea), "button-press-event",
		(GtkSignalFunc)DrawBoxButtonPressEvent, this);
	gtk_signal_connect(GTK_OBJECT(this->DrawingArea), "button-release-event",
		(GtkSignalFunc)DrawBoxButtonReleaseEvent, this);
	gtk_signal_connect(GTK_OBJECT(this->DrawingArea), "scroll-event",
		(GtkSignalFunc)DrawBoxScrollEvent, this);
	
	gtk_widget_set_events(this->DrawingArea, GDK_EXPOSURE_MASK
		| GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK
		| GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
		
	DrawBox::Instances.push_front(this);
}

/* Destructor.
 */
DrawBox::~DrawBox()
{
	DrawBox::Instances.remove(this);
}

/* Installs a mouse button callback.
 * Inputs:
 * -Func: Callback function.
 * -UserData: Pointer to be passed as UserData parameter to callback.
 * -First: true to insert at beginning of callback list, false to insert at end.
 */
void DrawBox::InstallButtonCallback(MouseButtonCallbackPtr Func, void *UserData, bool First)
{
	Callback C;
	C.MouseButton = Func;
	C.UserData = UserData;
	if(First) this->MouseButtonCallback.push_front(C);
	else this->MouseButtonCallback.push_back(C);
}

/* Removes a mouse button callback.
 * Inputs:
 * -Func: Callback function.
 */
void DrawBox::RemoveButtonCallback(MouseButtonCallbackPtr Func)
{
	for(std::list<DrawBox::Callback>::iterator
		i = this->MouseButtonCallback.begin();
		i != this->MouseButtonCallback.end(); i++)
		if((*i).MouseButton == Func) this->MouseButtonCallback.erase(i);
}

/* Installs a mouse motion callback.
 * Inputs:
 * -Func: Callback function.
 * -UserData: Pointer to be passed as UserData parameter to callback.
 * -First: true to insert at beginning of callback list, false to insert at end.
 */
void DrawBox::InstallMouseMoveCallback(MouseMoveCallbackPtr Func, void *UserData, bool First)
{
	Callback C;
	C.MouseMove = Func;
	C.UserData = UserData;
	if(First) this->MouseMoveCallback.push_front(C);
	else this->MouseMoveCallback.push_back(C);
}

/* Removes a mouse motion callback.
 * Inputs:
 * -Func: Callback function.
 */
void DrawBox::RemoveMouseMoveCallback(MouseMoveCallbackPtr Func)
{
	for(std::list<DrawBox::Callback>::iterator
		i = this->MouseMoveCallback.begin();
		i != this->MouseMoveCallback.end(); i++)
		if((*i).MouseMove == Func) this->MouseMoveCallback.erase(i);
}

/* Installs a mouse scroll callback.
 * Inputs:
 * -Func: Callback function.
 * -UserData: Pointer to be passed as UserData parameter to callback.
 * -First: true to insert at beginning of callback list, false to insert at end.
 */
void DrawBox::InstallMouseScrollCallback(MouseScrollCallbackPtr Func, void *UserData, bool First)
{
	Callback C;
	C.MouseScroll = Func;
	C.UserData = UserData;
	if(First) this->MouseScrollCallback.push_front(C);
	else this->MouseScrollCallback.push_back(C);
}

/* Removes a mouse scroll callback.
 * Inputs:
 * -Func: Callback function.
 */
void DrawBox::RemoveMouseScrollCallback(MouseScrollCallbackPtr Func)
{
	for(std::list<DrawBox::Callback>::iterator
		i = this->MouseScrollCallback.begin();
		i != this->MouseScrollCallback.end(); i++)
		if((*i).MouseScroll == Func) this->MouseScrollCallback.erase(i);
}

/* Installs a configure event callback.
 * Inputs:
 * -Func: Callback function.
 * -UserData: Pointer to be passed as UserData parameter to callback.
 * -First: true to insert at beginning of callback list, false to insert at end.
 */
void DrawBox::InstallConfigureCallback(ConfigureCallbackPtr Func, void *UserData, bool First)
{
	Callback C;
	C.Configure = Func;
	C.UserData = UserData;
	if(First) this->ConfigureCallback.push_front(C);
	else this->ConfigureCallback.push_back(C);
}

/* Removes a configure event callback.
 * Inputs:
 * -Func: Callback function.
 */
void DrawBox::RemoveConfigureCallback(ConfigureCallbackPtr Func)
{
	for(std::list<DrawBox::Callback>::iterator
		i = this->ConfigureCallback.begin();
		i != this->ConfigureCallback.end(); i++)
		if((*i).Configure == Func) this->ConfigureCallback.erase(i);
}

/* Installs an expose event callback.
 * Inputs:
 * -Func: Callback function.
 * -UserData: Pointer to be passed as UserData parameter to callback.
 * -First: true to insert at beginning of callback list, false to insert at end.
 */
void DrawBox::InstallExposeCallback(ExposeCallbackPtr Func, void *UserData, bool First)
{
	Callback C;
	C.Expose = Func;
	C.UserData = UserData;
	if(First) this->ExposeCallback.push_front(C);
	else this->ExposeCallback.push_back(C);
}

/* Removes an expose event callback.
 * Inputs:
 * -Func: Callback function.
 */
void DrawBox::RemoveExposeCallback(ExposeCallbackPtr Func)
{
	for(std::list<DrawBox::Callback>::iterator i = this->ExposeCallback.begin();
		i != this->ExposeCallback.end(); i++)
		if((*i).Expose == Func) this->ExposeCallback.erase(i);
}

/* Retrieves the backbuffer.
 * Returns: obvious.
 */
GdkPixmap* DrawBox::GetBackBuf()
{
	return this->BackBuf;
}

/* Retrieves the GtkDrawingArea widget itself.
 * Returns: obvious.
 */
GtkWidget* DrawBox::GetDrawingArea()
{
	return this->DrawingArea;
}

/* Retrieves the backbuffer width.
 * Returns: obvious.
 */
u32 DrawBox::GetWidth()
{
	return this->Width;
}

/* Retrieves the backbuffer height.
 * Returns: obvious.
 */
u32 DrawBox::GetHeight()
{
	return this->Height;
}

/* Sets the foreground colour.
 * Inputs:
 * -R, G, B: Colour (0-255).
 */
void DrawBox::SetFGColour(u8 R, u8 G, u8 B)
{
	GdkColor Colour;

	if(!this->DrawingArea) return;	
	this->FGRed = R;
	this->FGGreen = G;
	this->FGBlue = B;
	
	//Not a typo. Multiplying by 257 nicely maps to the range 0-65535 instead
	//of 0-65025.
	Colour.red = R * 257;
	Colour.green = G * 257;
	Colour.blue = B * 257;
	gdk_gc_set_rgb_fg_color(this->DrawingArea->style->black_gc, &Colour);
}

/* Plots a pixel in the foreground colour.
 * Inputs:
 * -X, Y: Coordinates of pixel.
 */
void DrawBox::DrawPixel(u32 X, u32 Y)
{
	if(!this->BackBuf || !this->DrawingArea) return;
	gdk_draw_point(this->BackBuf, this->DrawingArea->style->black_gc, X, Y);
}

/* Sets the foreground colour and plots a pixel in it.
 * Inputs:
 * -R, G, B: New foreground colour.
 * -X, Y: Coordinates of pixel.
 * Notes:
 * -For efficiency, does not set the colour if it's the same as the current one.
 */
void DrawBox::DrawPixel(u32 X, u32 Y, u8 R, u8 G, u8 B)
{
	if(!this->BackBuf) return;
	if((R != this->FGRed) || (G != this->FGGreen) || (B != this->FGBlue))
		this->SetFGColour(R, G, B);
	gdk_draw_point(this->BackBuf, this->DrawingArea->style->black_gc, X, Y);
}

/* Draws a rectangle in the foreground colour.
 * Inputs:
 * -X, Y: Coordinates of top left corner.
 * -Width, Height: Size of rectangle.
 * -Filled: true to fill, false to draw outline only.
 * Notes:
 * -If Width or Height are zero, the dimensions of the box minus the coordinate
 *  is used. So you can clear to a colour by passing zero for X, Y, Width and
 *  Height.
 */
void DrawBox::DrawRectangle(u32 X, u32 Y, u32 Width, u32 Height, bool Filled)
{
	if(!this->BackBuf || !this->DrawingArea) return;
	if(!Width) Width = this->DrawingArea->allocation.width - X;
	if(!Height) Height = this->DrawingArea->allocation.height - Y;
	gdk_draw_rectangle(this->BackBuf, this->DrawingArea->style->black_gc, Filled,
		X, Y, Width, Height);
}

/* Draws a line in the foreground colour.
 * Inputs:
 * -X1, Y1: Coordinates of start point.
 * -X2, Y2: Coordinates of end point.
 */
void DrawBox::DrawLine(u32 X1, u32 Y1, u32 X2, u32 Y2)
{
	if(!this->BackBuf || !this->DrawingArea) return;
	gdk_draw_line(this->BackBuf, this->DrawingArea->style->black_gc,
		X1, Y1, X2, Y2);
}

/* Draws an arc in the foreground colour.
 * Inputs:
 * -X, Y: Coordinates of top left edge of bounding rectangle.
 * -W, H: Size of bounding rectangle.
 * -Start: Start angle, in 1/64ths of a degree.
 * -End: End angle, relative to start, in 1/64ths of a degree.
 * -Filled: true to fill, false to draw outline only.
 * Notes:
 * -Angles are in standard position (i.e. 0 degrees is 3:00, 90 degrees is
 *  12:00, etc).
 */
void DrawBox::DrawArc(u32 X, u32 Y, u32 W, u32 H, s32 Start, s32 End, bool Filled)
{
	if(!this->BackBuf || !this->DrawingArea) return;
	gdk_draw_arc(this->BackBuf, this->DrawingArea->style->black_gc,
		Filled, X, Y, W, H, Start, End);
}

/* Draws text in the foreground colour.
 * Inputs:
 * -X, Y: Coordinates of text.
 * -Text: Text to draw. Can contain markup; see
 *  http://library.gnome.org/devel/pango/stable/PangoMarkupFormat.html
 */
void DrawBox::DrawText(u32 X, u32 Y, std::string Text)
{
	if(!this->BackBuf || !this->DrawingArea) return;
	if(!this->PLayout) this->PLayout =
		gtk_widget_create_pango_layout(this->DrawingArea, NULL);
	pango_layout_set_markup(this->PLayout, Text.c_str(), -1);
	gdk_draw_layout(this->BackBuf, this->DrawingArea->style->black_gc, X, Y,
		this->PLayout);
}

/* Blits (copies) part of another DrawBox.
 * Inputs:
 * -Src: Box to copy. A box can be copied onto itself.
 * -SX, SY: Coordinates to copy from.
 * -DX, DY: Coordniates to copy to.
 * -W, H: Size of rectangle to copy.
 */
void DrawBox::BlitFrom(DrawBox *Src, u32 SX, u32 SY, u32 DX, u32 DY, u32 W, u32 H)
{
	if(!this->BackBuf || !this->DrawingArea || !Src || !Src->BackBuf) return;
	gdk_draw_drawable(this->BackBuf, this->DrawingArea->style->black_gc,
		Src->BackBuf, SX, SY, DX, DY, W, H);
}

/* Blits (copies) part of an ImgBuf.
 * Inputs:
 * -Src: ImgBuf to copy.
 * -SX, SY: Coordinates to copy from.
 * -DX, DY: Coordniates to copy to.
 * -W, H: Size of rectangle to copy.
 */
void DrawBox::BlitFrom(ImgBuf *Src, u32 SX, u32 SY, u32 DX, u32 DY, u32 W, u32 H)
{
	ImgBuf::Pixel *Pixels;
	if(!this->BackBuf || !this->DrawingArea || !Src) return;
	Pixels = Src->GetPixels();
	if(!Pixels) return;
	Pixels = &Pixels[(SY * Src->GetWidth()) + SX];
	gdk_draw_rgb_32_image(this->BackBuf, this->DrawingArea->style->black_gc,
		DX, DY, W, H, GDK_RGB_DITHER_NORMAL, (const guchar*)Pixels,
		Src->GetWidth() * sizeof(ImgBuf::Pixel));
}

/* Invalidates the specified region.
 * Inputs:
 * -X, Y: Coordinates of region.
 * -Width, Height: Size of region.
 * Notes:
 * -The region will be redrawn once the current batch of events have been
 *  processed. Use this after drawing to the box.
 */
void DrawBox::InvalidateRegion(u32 X, u32 Y, u32 Width, u32 Height)
{
	if(!this->DrawingArea) return;
	gtk_widget_queue_draw_area(this->DrawingArea, X, Y, Width, Height);
}

/* Invalidates the entire drawing area.
 */
void DrawBox::InvalidateAll()
{
	if(!this->DrawingArea) return;
	gtk_widget_queue_draw(this->DrawingArea);
}

/* Resizes the buffer.
 * Inputs:
 * -Width, Height: New size.
 * Returns: true on success, false on failure.
 * Notes:
 * -The buffer contents will be lost. A configure_event and expose_event will be
 *  generated to redraw them.
 * -Any existing pointers to the back buffer will be invalidated.
 */
bool DrawBox::Resize(u32 Width, u32 Height)
{
	this->Width = Width;
	this->Height = Height;
	gtk_widget_set_size_request(this->DrawingArea, this->Width, this->Height);
	return true;
}

/* FRIEND: Retrieve a DrawBox given its DrawingArea.
 * Inputs:
 * -DrawingArea: DrawingArea to look up.
 * Returns: Pointer to DrawBox on success, NULL on failure.
 */
DrawBox* LookupDrawBox(GtkWidget *DrawingArea)
{
	for(std::list<DrawBox*>::iterator i = DrawBox::Instances.begin();
		i != DrawBox::Instances.end(); i++)
	{
		if(*i && (*i)->DrawingArea == DrawingArea) return *i;
	}
	return NULL;
}

/* FRIEND: Callback for button-press-event (mouse button down).
 * Inputs:
 * -Widget: Widget that received the event (i.e. the drawing area).
 * -Event: The event.
 * -Box: The DrawBox.
 * Returns: TRUE to stop other handlers, FALSE to propagate further.
 */
gboolean DrawBoxButtonPressEvent(GtkWidget *Widget, GdkEventButton *Event,
gpointer Box)
{
	DrawBox* _this = (DrawBox*)Box;
	
	//Call button event callbacks
	for(std::list<DrawBox::Callback>::iterator
		i = _this->MouseButtonCallback.begin();
		i != _this->MouseButtonCallback.end(); i++)
		if((*i).MouseButton)
			(*i).MouseButton(_this, Widget, Event, true, (*i).UserData);
	return TRUE;
}

/* FRIEND: Callback for button-release-event (mouse button up).
 * Inputs:
 * -Widget: Widget that received the event (i.e. the drawing area).
 * -Event: The event.
 * -Box: The DrawBox.
 * Returns: TRUE to stop other handlers, FALSE to propagate further.
 */
gboolean DrawBoxButtonReleaseEvent(GtkWidget *Widget, GdkEventButton *Event,
gpointer Box)
{
	DrawBox* _this = (DrawBox*)Box;
	
	//Call button event callbacks
	for(std::list<DrawBox::Callback>::iterator
		i = _this->MouseButtonCallback.begin();
		i != _this->MouseButtonCallback.end(); i++)
		if((*i).MouseButton)
			(*i).MouseButton(_this, Widget, Event, false, (*i).UserData);
	return TRUE;
}

/* FRIEND: Callback for motion-notify-event (mouse moved).
 * Inputs:
 * -Widget: Widget that received the event (i.e. the drawing area).
 * -Event: The event.
 * -Box: The DrawBox.
 * Returns: TRUE to stop other handlers, FALSE to propagate further.
 */
gboolean DrawBoxMotionEvent(GtkWidget *Widget, GdkEventMotion *Event,
gpointer Box)
{
	gint X, Y;
	GdkModifierType State;
	
	DrawBox* _this = (DrawBox*)Box;

	if(Event->is_hint)
		gdk_window_get_pointer(Event->window, &X, &Y, &State);
	else
	{
		X = Event->x;
		Y = Event->y;
		State = (GdkModifierType)Event->state;
	}

	//Call motion event callbacks
	for(std::list<DrawBox::Callback>::iterator
		i = _this->MouseMoveCallback.begin();
		i != _this->MouseMoveCallback.end(); i++)
		if((*i).MouseMove)
			(*i).MouseMove(_this, Widget, Event, (u32)X, (u32)Y, State,
			(*i).UserData);

	return TRUE;
}

/* FRIEND: Callback for scroll-event (mouse wheel scroll).
 * Inputs:
 * -Widget: Widget that received the event (i.e. the drawing area).
 * -Event: The event.
 * -Box: The DrawBox.
 * Returns: TRUE to stop other handlers, FALSE to propagate further.
 */
gboolean DrawBoxScrollEvent(GtkWidget *Widget, GdkEventScroll *Event,
gpointer Box)
{
	DrawBox* _this = (DrawBox*)Box;
	
	//Call scroll event callbacks
	for(std::list<DrawBox::Callback>::iterator
		i = _this->MouseScrollCallback.begin();
		i != _this->MouseScrollCallback.end(); i++)
		if((*i).MouseScroll)
			(*i).MouseScroll(_this, Widget, Event, (*i).UserData);
	return TRUE;
}

/* FRIEND: Callback for configure-event (window creation, resize, etc).
 * Inputs:
 * -Widget: Widget that received the event (i.e. the drawing area).
 * -Event: The event.
 * -Box: The DrawBox.
 * Returns: TRUE to stop other handlers, FALSE to propagate further.
 */
gboolean DrawBoxConfigureEvent(GtkWidget *Widget, GdkEventConfigure *Event,
gpointer Box)
{
	DrawBox* _this = (DrawBox*)Box;
	
	if(_this->BackBuf) gdk_pixmap_unref(_this->BackBuf);

	//Whenever the window is created or resized, create a backbuffer for it.
	_this->Width = Widget->allocation.width;
	_this->Height = Widget->allocation.height;
	_this->BackBuf = gdk_pixmap_new(Widget->window,
		_this->Width, _this->Height, -1);
		
	//Call configure event callbacks
	for(std::list<DrawBox::Callback>::iterator
		i = _this->ConfigureCallback.begin();
		i != _this->ConfigureCallback.end(); i++)
		if((*i).Configure)
			(*i).Configure(_this, Widget, Event, (*i).UserData);
	return TRUE;
}

/* FRIEND: Callback for expose-event (widget needs to be redrawn).
 * Inputs:
 * -Widget: Widget that received the event (i.e. the drawing area).
 * -Event: The event.
 * -Box: The DrawBox.
 * Returns: TRUE to stop other handlers, FALSE to propagate further.
 */
gboolean DrawBoxExposeEvent(GtkWidget *Widget, GdkEventExpose *Event,
gpointer Box)
{
	DrawBox* _this = (DrawBox*)Box;
	
	//Call expose event callbacks
	for(std::list<DrawBox::Callback>::iterator
		i = _this->ExposeCallback.begin();
		i != _this->ExposeCallback.end(); i++)
		if((*i).Expose)
			(*i).Expose(_this, Widget, Event, (*i).UserData);
	
 	gdk_draw_pixmap(Widget->window,
		Widget->style->fg_gc[GTK_WIDGET_STATE(Widget)], _this->BackBuf,
		Event->area.x, Event->area.y, Event->area.x, Event->area.y,
		Event->area.width, Event->area.height);
	return TRUE;
}
